#!/bin/sh
# Slackware remove package script
#
# Revision 1.9 Wed Oct 31 14:04:28 CDT 2007 volkerding
# - Fix problem removing packages with a large number of fields.
#   Thanks to Niki Kovacs for noticing this, and to Piter Punk
#   for the patch.
# - Use LC_ALL=C locale, which is much faster with "sort".
#   Thanks to Tsomi.
# - Don't try to remove any package that starts with '-'.  This
#   is not a proper package name (usually a typo), and results
#   in the package database being broken.  Thanks to Jef Oliver.
# - Patched cat_except() to allow the last Slackware package on
#   a partition to be removed (using ROOT=, of course)
#   Thanks to Selkfoster for the patch, and to everyone else who
#   proposed solutions before.  This issue really wasn't given
#   the highest priority before, but I figured while I'm in here...
#
# Revision 1.8 Thu Nov 22 14:00:13 PST 2001 volkerding Rel $
# - Move $TMP underneath $ROOT
# - Understand the idea of a base package name, so that packages
#   can be removed with any of these notations:
#   removepkg foo-1.0-i386-1.tgz
#   removepkg foo-1.0-i386-1
#   removepkg foo.tgz
#   removepkg foo
#
# Revision 1.7  2001/03/30 12:36:28 volkerding
# - Strip extra ".tgz" from input names.
#
# Revision 1.6  1999/03/25 18:26:41 volkerding
# - Use external $ROOT variable, like installpkg.
#
# Revision 1.5.1  1998/03/18 15:37:28 volkerding
# - Since removepkg is always run by root, the temp directory has been
#   moved from /tmp to a private directory to avoid symlink attacks from
#   malicious users.
#
# Revision 1.5  1997/06/26 12:09:53  franke
# - Fixed old bug in TRIGGER regex setting
# - -preserve/-copy options now preserve non-unique files
#   and empty directories also
#
# Revision 1.4  1997/06/09 13:21:36  franke
# - Package file preserve (-preserve, -copy) added.
# - Don't execute "rm -rf" lines from doinst.sh, removing links explicit.
# - Warning on no longer existing files added.
# - Warning on files changed after package installation added.
# - Intermediate file preserve (-keep) added.
# - Check for required files/links now done on a combined list.
# - Write access to /var/log/{packages,scripts} no longer necessary for -warn.
#
# Revision 1.3  1997/06/08 13:03:05  franke
# Merged with revision 1.1.1.1
#
# Revision 1.2  1996/06/01 20:04:26  franke
# Delete empty directories & formated manual pages added
#
# Revision 1.1.1.1  1995/12/18 21:20:42  volkerding
# Original Version from Slackware 3.1
#
# Revision 1.1  1995/06/05 22:49:11  volkerding
# Original Version from Slackware 3.0
#

# Copyright 1994, 1995, 1998  Patrick Volkerding, Moorhead, Minnesota USA
# Copyright 2001, Slackware Linux, Inc., Concord, CA USA
# All rights reserved.
#
# Redistribution and use of this script, with or without modification, is 
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
#  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
#  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
#  EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
#  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
#  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
#  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
#  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

# This makes "sort" run much faster:
export LC_ALL=C

# Make sure there's a proper temp directory:
TMP=$ROOT/var/log/setup/tmp
# If the $TMP directory doesn't exist, create it:
if [ ! -d $TMP ]; then
  rm -rf $TMP # make sure it's not a symlink or something stupid
  mkdir -p $TMP
  chmod 700 $TMP # no need to leave it open
fi
ADM_DIR=$ROOT/var/log
PRES_DIR=$TMP/preserved_packages

# This simple cat_except() should be used on the installer,
# since the busybox "find" can't handle the complex find
# syntax:
#cat_except() {
# ( cd "$1" && cat `ls * | sed "/^$2\$/d"` )
#}

# This version of cat_except() allows the last package to be
# removed when ROOT= is used:
cat_except() {
  ( cd "$1" && \
    if [ $(find . -type f -maxdepth 1 | wc -l) -ne 1 ]; then
      cat $(find . -type f -maxdepth 1 | grep -v "$2")
    fi
  )
}

extract_links() {
 sed -n 's,^( *cd \([^ ;][^ ;]*\) *; *rm -rf \([^ )][^ )]*\) *) *$,\1/\2,p'
}

preserve_file() {
 if [ "$PRESERVE" = "true" ]; then
  F="`basename "$1"`"
  D="`dirname "$1"`"
  if [ ! -d "$PRES_DIR/$PKGNAME/$D" ]; then
    mkdir -p "$PRES_DIR/$PKGNAME/$D" || return 1
  fi
  cp -p "$ROOT/$D/$F" "$PRES_DIR/$PKGNAME/$D" || return 1
 fi
 return 0
}

preserve_dir() {
 if [ "$PRESERVE" = "true" ]; then
  if [ ! -d "$PRES_DIR/$PKGNAME/$1" ]; then
    mkdir -p "$PRES_DIR/$PKGNAME/$1" || return 1
  fi
 fi
 return 0
}

keep_files() {
 while read FILE ; do
  if [ ! -d "$ROOT/$FILE" ]; then
   if [ -r "$ROOT/$FILE" ]; then
    echo "  --> $ROOT/$FILE was found in another package. Skipping."
    preserve_file "$FILE"
   else
    if [ "`echo $FILE | cut -b1-8`" != "install/" ]; then
     echo "WARNING: Nonexistent $ROOT/$FILE was found in another package. Skipping."
    fi
   fi
  else
   preserve_dir "$FILE"
  fi
 done
}

keep_links() {
 while read LINK ; do
  if [ -L "$ROOT/$LINK" ]; then
   echo "  --> $ROOT/$LINK (symlink) was found in another package. Skipping."
  else
   echo "WARNING: Nonexistent $ROOT/$LINK (symlink) was found in another package. Skipping."
  fi
 done
}

delete_files() {
 while read FILE ; do
  if [ ! -d "$ROOT/$FILE" ]; then
   if [ -r "$ROOT/$FILE" ]; then
    if [ "$ROOT/$FILE" -nt "$ADM_DIR/packages/$PKGNAME" ]; then
     echo "WARNING: $ROOT/$FILE changed after package installation."
    fi
    if [ ! "$WARN" = "true" ]; then
     echo "  --> Deleting $ROOT/$FILE"
     preserve_file "$FILE" && rm -f "$ROOT/$FILE"
    else
     echo "  --> $ROOT/$FILE would be deleted"
     preserve_file "$FILE"
    fi
   else
    echo "  --> $ROOT/$FILE no longer exists. Skipping."
   fi
  else
   preserve_dir "$FILE"
  fi
 done
}

delete_links() {
 while read LINK ; do
  if [ -L "$ROOT/$LINK" ]; then
   if [ ! "$WARN" = "true" ]; then
    echo "  --> Deleting symlink $ROOT/$LINK"
    rm -f $ROOT/$LINK
   else
    echo "  --> $ROOT/$LINK (symlink) would be deleted"
   fi
  else
   echo "  --> $ROOT/$LINK (symlink) no longer exists. Skipping."
  fi
 done
}

delete_dirs() {
 sort -r | \
 while read DIR ; do
  if [ -d "$ROOT/$DIR" ]; then
    if [ ! "$WARN" = "true" ]; then
      if [ `ls -a "$ROOT/$DIR" | wc -l` -eq 2 ]; then
        echo "  --> Deleting empty directory $ROOT/$DIR"
        rmdir "$ROOT/$DIR"
      else
        echo "WARNING: Unique directory $ROOT/$DIR contains new files"
      fi
    else
     echo "  --> $ROOT/$DIR (dir) would be deleted if empty"
    fi
  fi
 done
}

delete_cats() {
 sed -n 's,/man\(./[^/]*$\),/cat\1,p'  | \
 while read FILE ; do
   if [ -f "$ROOT/$FILE" ]; then
     if [ ! "$WARN" = "true" ]; then
       echo "  --> Deleting $ROOT/$FILE (fmt man page)"
       rm -f $ROOT/$FILE
     else
       echo "  --> $ROOT/$FILE (fmt man page) would be deleted"
     fi
   fi
 done
}

package_name() {
  STRING=`basename $1 .tgz`
  # If we don't do this, commands run later will take the '-' to be an option
  # and will destroy the package database.  Packages should not contain spaces
  # in them.  Normally this type of problem results from a command line typo.
  if [ "$(echo $STRING | cut -b 1)" = "-" ]; then
    STRING="malformed-package-name-detected"
  fi
  # Check for old style package name with one segment:
  if [ "`echo $STRING | cut -f 1 -d -`" = "`echo $STRING | cut -f 2 -d -`" ]; then
    echo $STRING
  else # has more than one dash delimited segment
    # Count number of segments:
    INDEX=1
    while [ ! "`echo $STRING | cut -f $INDEX -d -`" = "" ]; do
      INDEX=`expr $INDEX + 1`
    done
    INDEX=`expr $INDEX - 1` # don't include the null value
    # If we don't have four segments, return the old-style (or out of spec) package name:
    if [ "$INDEX" = "2" -o "$INDEX" = "3" ]; then
      echo $STRING
    else # we have four or more segments, so we'll consider this a new-style name:
      NAME=`expr $INDEX - 3`
      NAME="`echo $STRING | cut -f 1-$NAME -d -`"
      echo $NAME
      # cruft for later ;)
      #VER=`expr $INDEX - 2`
      #VER="`echo $STRING | cut -f $VER -d -`"
      #ARCH=`expr $INDEX - 1`
      #ARCH="`echo $STRING | cut -f $ARCH -d -`"
      #BUILD="`echo $STRING | cut -f $INDEX -d -`"
    fi
  fi
}

# Conversion to 'comm' utility by Mark Wisdom.
# is pretty nifty! :^)
remove_packages() {
 for PKGLIST in $* 
 do
  PKGNAME=`basename $PKGLIST .tgz`
  echo
  # If we don't have a package match here, then we will attempt to find
  # a package using the long name format (name-version-arch-build) for
  # which the base package name was given.  On a properly-managed machine,
  # there should only be one package installed with a given basename, but
  # we don't enforce this policy.  If there's more than one, only one will
  # be removed.  If you want to remove them all, you'll need to run
  # removepkg again until it removes all the same-named packages.
  if [ ! -e $ADM_DIR/packages/$PKGNAME ]; then
   SHORT="`package_name $PKGNAME`"
   for long_package in $ADM_DIR/packages/${PKGNAME}* ; do
    if [ "$SHORT" = "`package_name $long_package`" ]; then
     PKGNAME="`basename $long_package`"
    fi
   done
  fi

  if [ ! -e $ADM_DIR/packages/$PKGNAME ]; then
    long_package=$(ls -1 $ADM_DIR/packages/${PKGNAME}* | grep -m 1 "${PKGNAME}-[^-]*-[^-]*-[^-]*$")
    if [ -e "$long_package" ]; then
      PKGNAME=$(basename $long_package)
    fi
  fi

  if [ -r $ADM_DIR/packages/$PKGNAME ]; then
   if [ ! "$WARN" = true ]; then
    echo "Removing package $ADM_DIR/packages/$PKGNAME..."
   fi
   if fgrep "./" $ADM_DIR/packages/$PKGNAME 1> /dev/null 2>&1; then
    TRIGGER="^\.\/"
   else
    TRIGGER="FILE LIST:"
   fi
   if [ ! "$WARN" = true ]; then
    echo "Removing files:"
   fi
   sed -n "/$TRIGGER/,/^$/p" < $ADM_DIR/packages/$PKGNAME | \
    fgrep -v "FILE LIST:" | sort -u > $TMP/delete_list$$
   # Pat's new-new && improved pre-removal routine.
   cat_except $ADM_DIR/packages $PKGNAME | sort -u > $TMP/required_list$$
   if [ -r $ADM_DIR/scripts/$PKGNAME ]; then
    extract_links < $ADM_DIR/scripts/$PKGNAME | sort -u > $TMP/del_link_list$$
    cat_except $ADM_DIR/scripts $PKGNAME | extract_links | \
     sort -u > $TMP/required_links$$
    mv $TMP/required_list$$ $TMP/required_files$$
    sort -u $TMP/required_links$$ $TMP/required_files$$ > $TMP/required_list$$
    comm -12 $TMP/del_link_list$$ $TMP/required_list$$ | keep_links
    comm -23 $TMP/del_link_list$$ $TMP/required_list$$ | delete_links
   else
    cat $ADM_DIR/scripts/* | extract_links | \
     sort -u > $TMP/required_links$$
    mv $TMP/required_list$$ $TMP/required_files$$
    sort -u $TMP/required_links$$ $TMP/required_files$$ >$TMP/required_list$$
   fi
   comm -12 $TMP/delete_list$$ $TMP/required_list$$ | keep_files
   comm -23 $TMP/delete_list$$ $TMP/required_list$$ > $TMP/uniq_list$$
   delete_files < $TMP/uniq_list$$
   delete_dirs < $TMP/uniq_list$$
   delete_cats < $TMP/uniq_list$$
   if [ ! "$KEEP" = "true" ]; then
    rm -f $TMP/delete_list$$ $TMP/required_files$$ $TMP/uniq_list$$
    rm -f $TMP/del_link_list$$ $TMP/required_links$$ $TMP/required_list$$
   fi
   if [ "$PRESERVE" = "true" ]; then
    if [ -r $ADM_DIR/scripts/$PKGNAME ]; then
     if [ ! -d "$PRES_DIR/$PKGNAME/install" ]; then
      mkdir -p "$PRES_DIR/$PKGNAME/install"
     fi
     cp -p $ADM_DIR/scripts/$PKGNAME $PRES_DIR/$PKGNAME/install/doinst.sh
    fi
   fi
   if [ ! "$WARN" = "true" ]; then
    for DIR in $ADM_DIR/removed_packages $ADM_DIR/removed_scripts ; do
     if [ ! -d $DIR ] ; then mkdir -p $DIR ; chmod 755 $DIR ; fi
    done
    mv $ADM_DIR/packages/$PKGNAME $ADM_DIR/removed_packages
    if [ -r $ADM_DIR/scripts/$PKGNAME ]; then
     mv $ADM_DIR/scripts/$PKGNAME $ADM_DIR/removed_scripts
    fi
   fi
  else
   echo "No such package: $ADM_DIR/packages/$PKGNAME. Can't remove."
  fi
 done
}

if [ "$#" = "0" ]; then
  echo "Usage: `basename $0` [-copy] [-keep] [-preserve] [-warn] packagename ..."; exit 1
fi

while : ; do
 case "$1" in
  -copy) WARN=true; PRESERVE=true; shift;;
  -keep) KEEP=true; shift;;
  -preserve) PRESERVE=true; shift;;
  -warn) WARN=true; shift;;
  -*) echo "Usage: `basename $0` [-copy] [-keep] [-preserve] [-warn] packagename ..."; exit 1;;
  *) break
 esac
done

if [ "$WARN" = "true" ]; then
 echo "Only warning... not actually removing any files."
 if [ "$PRESERVE" = "true" ]; then
  echo "Package contents is copied to $PRES_DIR."
 fi
 echo "Here's what would be removed (and left behind) if you"
 echo "removed the package(s):"
 echo
else
 if [ "$PRESERVE" = "true" ]; then
  echo "Package contents is copied to $PRES_DIR."
 fi
fi

remove_packages $*

